﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;

namespace Microsoft.AspNetCore.Mvc.ApplicationModels;

/// <summary>
/// Constructs a <see cref="CompiledPageActionDescriptor"/> from an <see cref="PageApplicationModel"/>.
/// </summary>
internal static class CompiledPageActionDescriptorBuilder
{
    /// <summary>
    /// Creates a <see cref="CompiledPageActionDescriptor"/> from the specified <paramref name="applicationModel"/>.
    /// </summary>
    /// <param name="applicationModel">The <see cref="PageApplicationModel"/>.</param>
    /// <param name="globalFilters">Global filters to apply to the page.</param>
    /// <returns>The <see cref="CompiledPageActionDescriptor"/>.</returns>
    public static CompiledPageActionDescriptor Build(
        PageApplicationModel applicationModel,
        FilterCollection globalFilters)
    {
        var boundProperties = CreateBoundProperties(applicationModel);
        var filters = Enumerable.Concat(
                globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global)),
                applicationModel.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action)))
            .ToArray();
        var handlerMethods = CreateHandlerMethods(applicationModel);

        if (applicationModel.ModelType != null && applicationModel.DeclaredModelType != null &&
            !applicationModel.DeclaredModelType.IsAssignableFrom(applicationModel.ModelType))
        {
            var message = Resources.FormatInvalidActionDescriptorModelType(
                applicationModel.ActionDescriptor.DisplayName,
                applicationModel.ModelType.Name,
                applicationModel.DeclaredModelType.Name);

            throw new InvalidOperationException(message);
        }

        var actionDescriptor = applicationModel.ActionDescriptor;
        return new CompiledPageActionDescriptor(actionDescriptor)
        {
            ActionConstraints = actionDescriptor.ActionConstraints,
            AttributeRouteInfo = actionDescriptor.AttributeRouteInfo,
            BoundProperties = boundProperties,
            EndpointMetadata = CreateEndPointMetadata(applicationModel),
            FilterDescriptors = filters,
            HandlerMethods = handlerMethods,
            HandlerTypeInfo = applicationModel.HandlerType,
            DeclaredModelTypeInfo = applicationModel.DeclaredModelType,
            ModelTypeInfo = applicationModel.ModelType,
            RouteValues = actionDescriptor.RouteValues,
            PageTypeInfo = applicationModel.PageType,
            Properties = applicationModel.Properties,
        };
    }

    private static IList<object> CreateEndPointMetadata(PageApplicationModel applicationModel)
    {
        var handlerMetatdata = applicationModel.HandlerTypeAttributes;
        var endpointMetadata = applicationModel.EndpointMetadata;

        // It is criticial to get the order in which metadata appears in endpoint metadata correct. More significant metadata
        // must appear later in the sequence.
        // In this case, handlerMetadata is attributes on the Page \ PageModel, and endPointMetadata is configured via conventions. and
        // We consider the latter to be more significant.
        return Enumerable.Concat(handlerMetatdata, endpointMetadata).ToList();
    }

    // Internal for unit testing
    internal static HandlerMethodDescriptor[] CreateHandlerMethods(PageApplicationModel applicationModel)
    {
        var handlerModels = applicationModel.HandlerMethods;
        var handlerDescriptors = new HandlerMethodDescriptor[handlerModels.Count];

        for (var i = 0; i < handlerDescriptors.Length; i++)
        {
            var handlerModel = handlerModels[i];

            handlerDescriptors[i] = new HandlerMethodDescriptor
            {
                HttpMethod = handlerModel.HttpMethod,
                Name = handlerModel.HandlerName,
                MethodInfo = handlerModel.MethodInfo,
                Parameters = CreateHandlerParameters(handlerModel),
            };
        }

        return handlerDescriptors;
    }

    // internal for unit testing
    internal static HandlerParameterDescriptor[] CreateHandlerParameters(PageHandlerModel handlerModel)
    {
        var methodParameters = handlerModel.Parameters;
        var parameters = new HandlerParameterDescriptor[methodParameters.Count];

        for (var i = 0; i < parameters.Length; i++)
        {
            var parameterModel = methodParameters[i];

            parameters[i] = new HandlerParameterDescriptor
            {
                BindingInfo = parameterModel.BindingInfo,
                Name = parameterModel.ParameterName,
                ParameterInfo = parameterModel.ParameterInfo,
                ParameterType = parameterModel.ParameterInfo.ParameterType,
            };
        }

        return parameters;
    }

    // internal for unit testing
    internal static PageBoundPropertyDescriptor[] CreateBoundProperties(PageApplicationModel applicationModel)
    {
        var results = new List<PageBoundPropertyDescriptor>();
        var properties = applicationModel.HandlerProperties;
        for (var i = 0; i < properties.Count; i++)
        {
            var propertyModel = properties[i];

            // Only add properties which are explicitly marked to bind.
            if (propertyModel.BindingInfo == null)
            {
                continue;
            }

            var descriptor = new PageBoundPropertyDescriptor
            {
                Property = propertyModel.PropertyInfo,
                Name = propertyModel.PropertyName,
                BindingInfo = propertyModel.BindingInfo,
                ParameterType = propertyModel.PropertyInfo.PropertyType,
            };

            results.Add(descriptor);
        }

        return results.ToArray();
    }
}
